package it.uniroma2.art.coda.extension;

import it.uniroma2.art.coda.exception.FelixInitializationException;
import it.uniroma2.art.coda.interfaces.CODAExtensionInterface;
import it.uniroma2.art.coda.interfaces.CODAExtensionModuleInterface;

import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.jar.Attributes;
import java.util.jar.JarFile;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.felix.framework.Felix;
import org.apache.felix.framework.util.Util;
import org.apache.felix.main.Main;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.util.tracker.ServiceTracker;

/**
 * @author Andrea Turbati
 */

public abstract class ARTOSGiManager {

	protected Felix m_felix = null;
	private ArrayList<String> jarPresent = new ArrayList<String>();

	protected ArrayList<Bundle> bundleToBeStarted = new ArrayList<Bundle>();

	protected static Log logger = LogFactory.getLog(ARTOSGiManager.class);

	protected ARTOSGiManager(File felixDir, String fileProperty, String propertyName)
			throws FelixInitializationException {

		if (felixDir.exists() && !felixDir.isDirectory())
			throw new FelixInitializationException(
					"directory name for OSGi cache already associated to an existing file");

		URL defaultPropertiesURL = Main.class.getClassLoader().getResource("default.properties");
		Properties defaultProperties = loadProperties(defaultPropertiesURL);

		URL osgiPopertiesURL = this.getClass().getResource(fileProperty);
		// ARTOSGiManager.class.getResource("lw-osgi.properties"); //TODO CHECK IF THIS WORKS
		Properties osgiProperties = loadProperties(osgiPopertiesURL);

		// Create a case-insensitive configuration property map.
		Map<String, String> configMap = new HashMap<String, String>();

		// this removal is from Felix 1.4.0 on: felix.embedded.execution is no more needed since system.exit
		// is never called by the framework (now
		// in charge of the developer
		// configMap.put(FelixConstants.EMBEDDED_EXECUTION_PROP, "true");

		// Updates the org.osgi.framework.system.packages with Semantic Turkey specific packages
		// via the system bundle.
		configMap.put(Constants.FRAMEWORK_SYSTEMPACKAGES, defaultProperties
				.getProperty("org.osgi.framework.system.packages")
				+ ", " + osgiProperties.getProperty(propertyName));

		configMap.put("org.osgi.framework.storage", felixDir.getPath());

		try {

			// TODO check if this change is correct
			// Now create an instance of the framework with
			// our configuration properties and activator.
			// Create host activator;
			// List list = new ArrayList();
			// m_felix = new Felix(configMap, list);

			m_felix = new Felix(configMap);

			// Now Felix instance is started.
			m_felix.start();
			logger.info("Felix started"); 
		} catch (Exception ex) {
			throw new FelixInitializationException(ex);
		}
	}

	protected void startAllBundle() {
		for (int i = 0; i < bundleToBeStarted.size(); ++i) {
			try {
				// bundleToBeStarted.get(i).start();
				bundleToBeStarted.get(i).start();
				;
			} catch (BundleException e) {
				// TODO REMOVE THIS AND THROW THE EXCEPTION!!!
				System.out.println("error during bundle installation: " + e.getMessage());
				logger.debug("error during bundle installation: " + e.getMessage());
				e.printStackTrace();
			}
		}
	}

	/**
	 * This methods looks for OSGi bundles in directory <code>dir</code>
	 * 
	 * @param dir
	 */
	protected void findAndInstallPlugin(File dir) {
		if (dir.isFile()) {
			// It is not a directory, but it is a file
			return;
		}
		FilenameFilter filterJar = new JarFilter();
		File[] fileList = dir.listFiles(filterJar);
		if (fileList == null)
			return;
		for (int i = 0; i < fileList.length; ++i) {
			if (isBundle(fileList[i])) {
				installBundle(fileList[i]);
			}
		}

	}

	/**
	 * Install a bundle
	 * 
	 * @param file
	 *            bundle to be loaded
	 */
	private void installBundle(File fileJar) {
		Bundle bundle = null;
		String bundleJarFileURIString = fileJar.toURI().toString();

		bundle = getBundleByLocation(bundleJarFileURIString);

		// contPlugin++;
		// mapIdDirPlugin.put(contPlugin, fileJar.getParent());

		if (bundle == null) { // The bundle is not installed, so it will be installed
			try {
				logger.info("bundle: " + fileJar + " is not present");
				bundle = m_felix.getBundleContext().installBundle(bundleJarFileURIString);
				logger.info("bundle: " + fileJar + " is being loaded");
				// bundle.start();
				bundleToBeStarted.add(bundle);
				jarPresent.add(bundle.getLocation());
				logger.info("bundle: " + fileJar + " started"); 
			} catch (BundleException e) {
				// TODO REMOVE THIS AND THROW THE EXCEPTION!!!
				System.out.println("error during bundle installation: " + e.getMessage());
				logger.debug("error during bundle installation: " + e.getMessage());
				e.printStackTrace();
			}
		} else {
			// The bundle was installed in a previous session, but now it has to be checked if it has been
			// updated
			jarPresent.add(bundle.getLocation());
			logger.info("bundle: " + fileJar + " is present"); 
			if (bundle.getLastModified() < fileJar.lastModified()) { // il bundle � stato modificato
				logger.info("bundle: " + fileJar + " has been modified"); 
				try {
					bundle.stop();
					bundle.uninstall();
					bundle = m_felix.getBundleContext().installBundle(bundleJarFileURIString);
					bundleToBeStarted.add(bundle);
					// bundle.start();
				} catch (BundleException e) {
					e.printStackTrace();
				}
			}
			// The bundle it is up to date, there is no need to do anything
		}
	}

	/**
	 * retrieves bundles associated to a given location
	 * 
	 * @param location
	 *            location del bundle ricercato
	 * @return null if no bundle corresponds to the given location
	 */
	private Bundle getBundleByLocation(String location) {
		Bundle bundles[] = m_felix.getBundleContext().getBundles();
		Bundle bundle = null;
		for (int i = 0; i < bundles.length; ++i) {
			if (bundles[i].getLocation().equals(location)) {
				bundle = bundles[i];
				break;
			}
		}
		return bundle;
	}

	/**
	 * This method is a sort of garbage collector, removing all previously installed bundles belonging to
	 * extensions which have been removed
	 */
	public void removeOldBundle() {
		Bundle[] bundles = m_felix.getBundleContext().getBundles();
		for (int i = 0; i < bundles.length; ++i) {
			if (!(jarPresent.contains(bundles[i].getLocation()))
					&& !(bundles[i].getLocation().equals("System Bundle"))) {
				logger.info("removing bundle: " + bundles[i].getLocation());
				try {
					bundles[i].stop();
					bundles[i].uninstall();
				} catch (BundleException e) {
					// TODO REMOVE THIS AND THROW THE EXCEPTION!!!
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * Stops the Felix environment. Not really used at the moment :-)
	 */
	protected void stopFelix() {
		try {
			m_felix.stop();
		} catch (BundleException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Checks if a given file is an OSGi bundle
	 * 
	 * @param file
	 *            the file to be checked
	 * @return <code>true</code> if <code>file</code> is and OSGi bundle
	 */
	private static boolean isBundle(File file) {
		try {
			JarFile jarFile = new JarFile(file);
			Attributes att = jarFile.getManifest().getMainAttributes();
			if (att.getValue("Bundle-Activator") != null) {
				return true;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * class providing a FilenameFilter implementation for recognizing jar files
	 * 
	 * @author Armando Stellato <stellato@info.uniroma2.it>, Andrea Turbati <turbati@info.uniroma2.it>
	 */
	protected static class JarFilter implements FilenameFilter {
		public boolean accept(File dir, String name) {
			return (name.endsWith(".jar"));
		}
	}

	protected static class DirFilter implements FileFilter {

		public boolean accept(File file) {
			return file.isDirectory();
		}

	}

	/**
	 * Retrieves property files for adding standard java packages (javax etc..) to be exported from this OSGi
	 * application
	 * 
	 * @param propURL
	 *            the url of the property file
	 * @return the properties
	 */
	public static Properties loadProperties(URL propURL) {
		// this portion of code (try-catch block) is borrowed from ExtensionManager in felix jar
		Properties props = new Properties();
		InputStream is = null;

		try {
			is = propURL.openConnection().getInputStream();
			props.load(is);
			is.close();
			// Perform variable substitution for system properties.
			for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();) {
				String name = (String) e.nextElement();
				props.setProperty(name, Util.substVars(props.getProperty(name), name, null, props));
			}
			logger.info("Loading properties: " + propURL);
		} catch (Exception ex2) {
			// Try to close input stream if we have one.
			try {
				if (is != null)
					is.close();
			} catch (IOException ex3) {
				// Nothing we can do.
			}
			logger.error("Unable to load any configuration properties: " + propURL);
		}
		return props;
	}

	@SuppressWarnings("unchecked")
	protected <T extends CODAExtensionModuleInterface> T getServletExtensionByID(String idRepImpl,
			Class<T> type) {
		ServiceTracker m_tracker = null;
		T repImpl = null;

		m_tracker = new ServiceTracker(m_felix.getBundleContext(), type.getName(), null);
		m_tracker.open();

		Object[] services = m_tracker.getServices();
		for (int i = 0; (services != null) && i < services.length; ++i) {
			if (((T) services[i]).getId().equals(idRepImpl)) {
				repImpl = (T) services[i];
				break;
			}
		}
		m_tracker.close();
		return repImpl;
	}

	protected ArrayList<String> getServletExtensionsIDForType(
			Class<? extends CODAExtensionModuleInterface> type) {
		ArrayList<String> servletExtensionsList = new ArrayList<String>();

		ServiceTracker m_tracker = null;
		m_tracker = new ServiceTracker(m_felix.getBundleContext(), type.getName(), null);
		m_tracker.open();

		Object[] services = m_tracker.getServices();
		for (int i = 0; (services != null) && i < services.length; ++i) {
			servletExtensionsList.add(((CODAExtensionModuleInterface) services[i]).getId());
		}
		m_tracker.close();

		return servletExtensionsList;
	}

	@SuppressWarnings("unchecked")
	protected <T extends CODAExtensionInterface> ArrayList<T> getServletExtensionsForType(Class<T> type) {
		ArrayList<T> servletExtensionsList = new ArrayList<T>();

		ServiceTracker m_tracker = null;
		m_tracker = new ServiceTracker(m_felix.getBundleContext(), type.getName(), null);
		m_tracker.open();

		Object[] services = m_tracker.getServices();
		for (int i = 0; (services != null) && i < services.length; ++i) {
			servletExtensionsList.add((T) services[i]);
		}
		m_tracker.close();

		return servletExtensionsList;
	}

}
